#include "hash_map.h"
#include "../common/v_common.h"

hash_map* bh_hash_map_create(uint32_t size, bool use_lock, hash_func hash_func,
                   key_equal_func key_equal_func, key_destroy_func key_destroy_func,
                   value_destroy_func value_destroy_func)
{
    hash_map* map;
    uint32_t total_size;

    if (size < HASH_MAP_MIN_SIZE) {
        size = HASH_MAP_MIN_SIZE;
    } else if (size > HASH_MAP_MAX_SIZE) {
        LOG_ERROR("hash_map create failed: size is too large.\n");
        return NULL;
    }

    if (!hash_func || !key_equal_func) {
        LOG_ERROR("hash_map create failed: hash function or key equal function is NULL.\n");
        return NULL;
    }

    total_size = offsetof(hash_map, elements)       //哈希结构大小
                 + sizeof(hash_map_elem*) * size    //哈希映射表大小
                 + (use_lock ? sizeof(korp_mutex) : 0);     //互斥锁大小

    map = (hash_map*)v_malloc((uint32_t)total_size);
    if ((total_size >= UINT32_MAX) || !map) {
        LOG_ERROR("hash_map create failed: alloc memory failed.\n");
        return NULL;
    }

    memset(map, 0, (uint32_t)total_size);

    if (use_lock) {
        map->lock = (korp_mutex*)((uint8_t*)map + offsetof(hash_map, elements)
                                   + sizeof(hash_map_elem*) * size);
        if (os_mutex_init(map->lock)) {
            LOG_ERROR("hash_map create failed: init map lock failed.\n");
            v_free(map);
            return NULL;
        }
    }

    map->size = size;
    map->hash_func = hash_func;
    map->key_equal_func = key_equal_func;
    map->key_destroy_func = key_destroy_func;
    map->value_destroy_func = value_destroy_func;

    return map;
}

bool bh_hash_map_insert(hash_map* map, void* key, void* value)
{
    uint32_t index;
    hash_map_elem* elem;

    if (!map || !key) {
        LOG_ERROR("hash_map insert elem failed: map or key is NULL.\n");
        return false;
    }

    if (map->lock) {
        os_mutex_lock(map->lock);
    }

    index = map->hash_func(key) % map->size;
    elem = map->elements[index];
    while (elem) {
        if (map->key_equal_func(elem->key, key)) {
            LOG_ERROR("hash_map insert elem failed: duplicated key found.\n");
            goto fail;
        }
        elem = elem->next;
    }

    elem = (hash_map_elem*)v_malloc(sizeof(hash_map_elem));
    if (!elem) {
        LOG_ERROR("hash_map insert elem failed: alloc memory failed.\n");
        goto fail;
    }

    elem->key = key;
    elem->value = value;
    elem->next = map->elements[index];
    map->elements[index] = elem;

    if (map->lock) {
        os_mutex_unlock(map->lock);
    }
    return true;

fail:
    if (map->lock) {
        os_mutex_unlock(map->lock);
    }
    return false;
}

void* bh_hash_map_find(hash_map* map, void* key)
{
    uint32_t index;
    hash_map_elem* elem;
    void* value;

    if (!map || !key) {
        LOG_ERROR("hash_map find elem failed: map or key is NULL.\n");
        return NULL;
    }

    if (map->lock) {
        os_mutex_lock(map->lock);
    }

    index = map->hash_func(key) % map->size;
    elem = map->elements[index];

    while (elem) {
        if (map->key_equal_func(elem->key, key)) {
            value = elem->value;
            if (map->lock) {
                os_mutex_unlock(map->lock);
            }
            return value;
        }
        elem = elem->next;
    }

    if (map->lock) {
        os_mutex_unlock(map->lock);
    }
    return NULL;
}

bool bh_hash_map_update(hash_map* map, void* key, void* value, void** p_old_value)
{
    uint32_t index;
    hash_map_elem* elem;

    if (!map || !key) {
        LOG_ERROR("hash_map update elem failed: map or key is NULL.\n");
        return false;
    }

    if (map->lock) {
        os_mutex_lock(map->lock);
    }

    index = map->hash_func(key) % map->size;
    elem = map->elements[index];

    while (elem) {
        if (map->key_equal_func(elem->key, key)) {
            if (p_old_value) {
                *p_old_value = elem->value;
            }
            elem->value = value;
            if (map->lock) {
                os_mutex_unlock(map->lock);
            }
            return true;
        }
        elem = elem->next;
    }

    if (map->lock) {
        os_mutex_unlock(map->lock);
    }
    return false;
}

bool bh_hash_map_remove(hash_map* map, void* key, void** p_old_key,
                   void** p_old_value)
{
    uint32_t index;
    hash_map_elem* elem, *prev;

    if (!map || !key) {
        LOG_ERROR("hash_map remove elem failed: map or key is NULL.\n");
        return false;
    }

    if (map->lock) {
        os_mutex_lock(map->lock);
    }

    index = map->hash_func(key) % map->size;
    prev = elem = map->elements[index];

    while (elem) {
        if (map->key_equal_func(elem->key, key)) {
            if (p_old_key)
                *p_old_key = elem->key;
            if (p_old_value)
                *p_old_value = elem->value;

            if (elem == map->elements[index])
                map->elements[index] = elem->next;
            else
                prev->next = elem->next;

            v_free(elem);

            if (map->lock) {
                os_mutex_unlock(map->lock);
            }
            return true;
        }

        prev = elem;
        elem = elem->next;
    }

    if (map->lock) {
        os_mutex_unlock(map->lock);
    }
    return false;
}

bool bh_hash_map_destroy(hash_map* map)
{
    uint32_t index;
    hash_map_elem* elem,* next;

    if (!map) {
        LOG_ERROR("hash_map destroy failed: map is NULL.\n");
        return false;
    }

    if (map->lock) {
        os_mutex_lock(map->lock);
    }

    for (index = 0; index < map->size; index++) {
        elem = map->elements[index];
        while (elem) {
            next = elem->next;

            if (map->key_destroy_func) {
                map->key_destroy_func(elem->key);
            }
            if (map->value_destroy_func) {
                map->value_destroy_func(elem->value);
            }
            v_free(elem);

            elem = next;
        }
    }

    if (map->lock) {
        os_mutex_unlock(map->lock);
        os_mutex_destroy(map->lock);
    }
    v_free(map);
    return true;
}

uint32_t bh_hash_map_get_struct_size(hash_map* hashmap)
{
    uint32_t size = (uint32_t)(uintptr_t)offsetof(hash_map, elements)
                  + (uint32_t)sizeof(hash_map_elem* ) * hashmap->size;

    if (hashmap->lock) {
        size += (uint32_t)sizeof(korp_mutex);
    }

    return size;
}

uint32_t bh_hash_map_get_elem_struct_size()
{
    return (uint32_t)sizeof(hash_map_elem);
}

bool bh_hash_map_traverse(hash_map* map, traverse_func_cb callback,
                     void* user_data)
{
    uint32_t index;
    hash_map_elem* elem,* next;

    if (!map || !callback) {
        LOG_ERROR("hash_map traverse failed: map or callback is NULL.\n");
        return false;
    }

    if (map->lock) {
        os_mutex_lock(map->lock);
    }

    for (index = 0; index < map->size; index++) {
        elem = map->elements[index];
        while (elem) {
            next = elem->next;
            callback(elem->key, elem->value, user_data);
            elem = next;
        }
    }

    if (map->lock) {
        os_mutex_unlock(map->lock);
    }

    return true;
}
